package tw.com.hitevision.whiteboard.android.stateFile;

import android.Manifest;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Environment;
import android.util.Log;

import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
import com.bumptech.glide.request.FutureTarget;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;

import androidx.core.content.ContextCompat;
import tw.com.hitevision.whiteboard.android.Settings;
import tw.com.hitevision.whiteboard.android.util.GlideApp;

public class StateFileSaver {
    private final static String TAG = StateFileSaver.class.getSimpleName();

    private static final String FOLDER_WHITEBOARD = "Whiteboard";
    private static final String JSON_FILE_PREFIX = "Page";
    private static final String JSON_FILE_SUFFIX = ".json";
    private static final String PRIVATE_JSON_IMAGE_FOLDER = "jsonImage";
    private static final String PRIVATE_JSON_FOLDER_PREFIX = "json";
    private static final String PRIVATE_JSON_FOLDER_ANNOTATION = "Annotation";

    private File jsonFolder;
    private File jsonImageFolder;
    private String oldJsonFolderName;
    private LoadJsonToStateFileListener listener;
    private final Settings settings;
    private final Context context;

    private static class StateFileSaverHolder {
        private static StateFileSaver instance;
        private static void createStateFileSaver(Context context) {
            instance = new StateFileSaver(context);
        }
    }

    public static synchronized StateFileSaver getInstance(Context context) {
        if (StateFileSaverHolder.instance == null) {
            StateFileSaverHolder.createStateFileSaver(context);
        }
        return StateFileSaverHolder.instance;
    }

    public void reset() {
        jsonFolder = null;
        jsonImageFolder = null;
        oldJsonFolderName = null;
    }

    private StateFileSaver(Context context) {
        this.context = context.getApplicationContext();
        settings = Settings.getInstance(context);
    }

    private String generateJsonFolderName() {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.US); // Format date
        String date = dateFormat.format(Calendar.getInstance().getTime());
        long currentTime = System.currentTimeMillis();

        return PRIVATE_JSON_FOLDER_PREFIX + "_" + date + "_" + currentTime;
    }

    private String generateJsonFolderPath(Context context, String folderName) {
        ContextWrapper cw = new ContextWrapper(context);
        return cw.getFilesDir().getPath() + File.separator + FOLDER_WHITEBOARD
                + File.separator + folderName;
    }

    public boolean setJsonFolderToOldFolder() {
        if (oldJsonFolderName != null && oldJsonFolderName.length() > 0) {
           createJsonFolder(context, oldJsonFolderName);
           oldJsonFolderName = null;
        }

        return checkJsonFolderExist();
    }

    public void loadJsonFolder(String name) {
        if (!hasStoragePermission()) {
            return;
        }

        createJsonFolder(context, name);
    }

    private void createJsonFolder(Context context, final String name) {
        log("Json: createJsonFolder : " + name);
        String jsonFolderPath = generateJsonFolderPath(context, name);
        jsonFolder = new File(jsonFolderPath);

        String jsonFolderName = name;

        if (!jsonFolder.exists()) {
            if (!jsonFolder.mkdirs()) {
                log("Json: Error when create json folder.");
                jsonFolderName = null;
            }
        }

        if (jsonFolder.exists()) {
            jsonImageFolder = new File(jsonFolderPath + File.separator + PRIVATE_JSON_IMAGE_FOLDER);
            if (!jsonImageFolder.exists()) {
                if (!jsonImageFolder.mkdirs()) {
                    log("Json: Error when create json image folder.");
                }
            }
        }

        if (jsonFolderName != null) {
            String backupName = settings.getJsonSaveFolder();
            if (!backupName.equals(jsonFolderName)) {
                deleteJsonFolder(backupName);
            }
            settings.setJsonSaveFolder(jsonFolderName);
        }
    }

    public void setListener(LoadJsonToStateFileListener listener) {
        this.listener = listener;
    }

    private boolean hasStoragePermission() {
        return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }

    private boolean checkJsonFolderExist() {
        boolean result = (jsonFolder != null && jsonFolder.exists());
        if (!result) {
            log("Json: Json folder does not exist.");
        }
        return result;
    }

    private boolean checkJsonImageFolderExist() {
        boolean result = (jsonImageFolder != null && jsonImageFolder.exists());
        if (!result) {
            log("Json: Json image folder does not exist.");
        }
        return result;
    }

    void loadJsonToStateFile() {
        int stateSize = 0;

        if (listener != null) {
            stateSize = listener.getCurrentStateSize();
        }

        if (!checkJsonFolderExist()) {
            return;
        }

        // whiteboard pages
        for (int i = 0; i < stateSize; i++) {
            File file = new File(jsonFolder + File.separator + generateJsonFileName(i));

            if (file.exists()) {
                String jsonStr = loadFileToString(file);

                if (listener != null) {
                    listener.whiteboardJsonStringReadyToLoad(jsonStr, i);
                }
            }
        }

        // annotation page
        File file = new File(jsonFolder + File.separator + generateAnnotationJsonFileName());

        if (file.exists()) {
            String jsonStr = loadFileToString(file);

            if (listener != null) {
                listener.annotationJsonStringReadyToLoad(jsonStr);
            }
        }

        if (listener != null) {
            listener.blockTouchDuringDealingJson(false);
        }
    }

    private String loadFileToString(File file) {
        try {
            InputStream inputStream = new FileInputStream(file);

            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String jsonStr;
            String s;

            StringBuilder stringBuilder = new StringBuilder();

            while ((s = bufferedReader.readLine()) != null) {
                stringBuilder.append(s);
            }

            inputStream.close();

            jsonStr = stringBuilder.toString();

            return jsonStr;

        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    public String generateJsonFileName(int index) {
        return JSON_FILE_PREFIX + index + JSON_FILE_SUFFIX;
    }

    public String generateAnnotationJsonFileName() {
        return JSON_FILE_PREFIX + PRIVATE_JSON_FOLDER_ANNOTATION + JSON_FILE_SUFFIX;
    }

    void saveJsonToFile(String fileName, WhiteboardStateFile whiteboardStateFile) {
        if (!hasStoragePermission()) {
            return;
        }

        if (!checkJsonFolderExist()) {
            createJsonFolder(context, generateJsonFolderName());
            if (!checkJsonFolderExist()) {
                return;
            }
        }

        File file = new File(jsonFolder, fileName);

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
//            String jsonStr = GsonInstance.getGsonInstance().toJson(whiteboardStateFile);
            WhiteboardStateFile whiteboardStateFileCopy = whiteboardStateFile.copy();
            String jsonStr = GsonInstance.getGsonInstance().toJson(whiteboardStateFileCopy);

            FileOutputStream outputStream = new FileOutputStream(file);
            outputStream.write(jsonStr.getBytes());
            outputStream.flush();
            outputStream.close();
            log("Json: file<" + fileName + "> is saved.");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    void deleteJsonFiles(int[] ids, int statesSize) {
        log("Json: deleteJsonFiles ids : " + Arrays.toString(ids) + ", size : " + statesSize);

        if (!checkJsonFolderExist()) {
            return;
        }

        for (int id : ids) {
            String fileName = generateJsonFileName(id);
            File file = new File(jsonFolder, fileName);

            if (file.exists()) {
                if (file.delete()) {
                    log("Json: file <" + fileName + "> is deleted.");
                } else {
                    log("Json: fail when delete file <" + fileName + "> .");
                }
            }
        }

        if (statesSize == ids.length) {
            return;
        }

        List<Integer> deletedQ = new ArrayList<>();

        for (int i = 0;i < statesSize;i++) {
            String fileName = generateJsonFileName(i);
            File file = new File(jsonFolder, fileName);

            if (!file.exists()) {
                deletedQ.add(i);
            } else {
                if (deletedQ.size() > 0) {
                    int next = deletedQ.get(0);
                    if (i > next) {
                        String toName = generateJsonFileName(next);
                        File to = new File(jsonFolder, toName);

                        file.renameTo(to);
                        log("Json: file <" + fileName + "> is renamed to file <" + toName + ">");
                        deletedQ.remove(0);
                        deletedQ.add(i);
                    }
                }
            }
        }

    }

    public void updateJsonNameAfterInsertNewPage(int insertId, int stateSize) {
        log("Json: updateJsonNameAfterInsertNewPage, insertId : " + insertId + ", stateSize : " + stateSize);
        if (!checkJsonFolderExist()) {
//            return;
            createJsonFolder(context, generateJsonFolderName());
            if (!checkJsonFolderExist()) {
                return;
            }
        }

        if (insertId >= stateSize) {
            return;
        }

        for (int i = insertId;i < stateSize;i++) {
            String fromName = generateJsonFileName(i);
            File file = new File(jsonFolder, fromName);

            if (file.exists()) {
                String toName = generateJsonFileName(i + 1);
                File to = new File(jsonFolder, toName);

                file.renameTo(to);
                log("Json: file <" + fromName + "> is renamed to file <" + toName + ">");
            } else {
                break;
            }
        }
    }

    void copyJsonFiles(int[] ids, int size) {
        log("Json: copyJsonFiles ids : " + Arrays.toString(ids) + ", size : " + size);

        if (!checkJsonFolderExist()) {
//            return;
            createJsonFolder(context, generateJsonFolderName());
            if (!checkJsonFolderExist()) {
                return;
            }
        }

        for (int i = 0;i < ids.length;i++) {
            String srcName = generateJsonFileName(ids[i]);
            File src = new File(jsonFolder, srcName);
            String destName = generateJsonFileName(size + i);
            File dest = new File(jsonFolder, destName);
            copy(src, dest);
        }

    }

    private void copy(File src, File dst) {
        try {
            FileInputStream inStream = new FileInputStream(src);
            FileOutputStream outStream = new FileOutputStream(dst);
            FileChannel inChannel = inStream.getChannel();
            FileChannel outChannel = outStream.getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
            inStream.close();
            outStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    void saveJsonImageToInternalStorage(Bitmap bitmap, String fileName) {
        if (!checkJsonFolderExist()) {
            createJsonFolder(context, generateJsonFolderName());
            if (!checkJsonFolderExist()) {
                return;
            }
        }

        if (!checkJsonImageFolderExist()) {
            return;
        }

        File imageDir = new File(jsonImageFolder, fileName);

        if (!imageDir.exists()) {
            try {
                imageDir.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            // We use ID_GENERATOR.getAndIncrement() to generate the atomic id and use this id to create the image name.
            // The same images will have the same name. If the file exists, this image file has been created earlier.
            log("Json: the image <" + imageDir.getPath() + "> exists.");
            return;
        }

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(imageDir);
            // Use the compress method on the BitMap object to write image to the OutputStream
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
            log("Json: json image file saved : " + fileName);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public Bitmap getJsonImage(String imageFileName) {
        if (!checkJsonImageFolderExist()) {
            return null;
        }

        // hitevision debug
        if (imageFileName == null) {
            return null;
        }

        File imageDir = new File(jsonImageFolder, imageFileName);

        if (imageDir.exists()) {
//            try {
//                return BitmapFactory.decodeStream(new FileInputStream(imageDir));
//            } catch (FileNotFoundException e) {
//                e.printStackTrace();
//            }
            FutureTarget<Bitmap> target = GlideApp.with(context)
                    .asBitmap()
                    .downsample(DownsampleStrategy.CENTER_INSIDE)
                    .load(imageDir)
                    .timeout(1000)
                    .submit();
            try {
                return target.get();
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
                return null;
            }
        } else {
            log("Json: getJsonImage, the image does not exist : " + imageDir);
        }

        return null;
    }

    private void deleteJsonFolder(String jsonFolderName) {
        if (jsonFolderName !=null && jsonFolderName.length() > 0 &&
                !jsonFolderName.equals(Environment.getExternalStorageDirectory().getPath()) && !jsonFolderName.contains(File.separator)) {
            File jsonFolder = new File(generateJsonFolderPath(context, jsonFolderName));

            if (jsonFolder.exists() && !jsonFolder.getPath().equals( Environment.getExternalStorageDirectory().getPath())) {
                new Thread(() -> {
                    if (deleteDirectory(jsonFolder)) {
                        log("Json: folder <" + jsonFolder.getPath() + "> has been deleted.");
                    } else {
                        log("Json: fail when delete folder <" + jsonFolder.getPath() + ">");
                    }
                }).start();
            }
        }
    }

    private boolean deleteDirectory(File folder) {
        log("Json: deleteDirectory folder : " + folder.getName());

        if (folder.exists() && folder.isDirectory()) {
            File[] files = folder.listFiles();

            if (files == null) {
                log("Json: files is null.");
                return false;
            }

            for (File file : files) {
                if (file.isDirectory()) {
                    deleteDirectory(file);
                } else {
                    if (!file.delete()) {
                        log("Json: deleteDirectory : fail when delete " + file.getPath());
                    }
                }
            }

        }

        return folder.delete();
    }

    private void log(String s) {
        Log.d(TAG, s);
    }

    public interface LoadJsonToStateFileListener {

        void whiteboardJsonStringReadyToLoad(String jsonStr, int index);

        void annotationJsonStringReadyToLoad(String jsonStr);

        int getCurrentStateSize();

        void blockTouchDuringDealingJson(boolean block);

    }

}
